package com.fsbm.ctech.maxis.ebpsbackend;

import java.io.*;
import java.text.*;
import java.nio.channels.FileChannel;
import java.util.zip.*;
import java.util.*;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.*;

//import com.fsbm.ctech.maxis.ebpsbackend.ConfigObject;

/**
 * Class ZipAndLoad
 *
 * @author Leandro II R. Tayag
 */
public class ZipAndLoad {

	private static ConfigObject cfgObj = new ConfigObject();

	private static String zipInDirectory = cfgObj.zipAndLoad_ZIP_InDirectory;
	private static String zipOutDirectory = cfgObj.zipAndLoad_ZIP_OutDirectory;
	private static String zipDoneDirectory = cfgObj.zipAndLoad_ZIP_DoneDirectory;
	private static String zipErrorDirectory = cfgObj.zipAndLoad_ZIP_ErrorDirectory;

	private static String loadInDirectory = cfgObj.zipAndLoad_LOAD_InDirectory;
	private static String loadOutDirectory = cfgObj.zipAndLoad_LOAD_OutDirectory;
	private static String loadDoneDirectory = cfgObj.zipAndLoad_LOAD_DoneDirectory;
	private static String loadErrorDirectory = cfgObj.zipAndLoad_LOAD_ErrorDirectory;

	private static String FILE_EXTENSION_XLS = ".xls";
	private static String FILE_EXTENSION_CSV = ".csv";
	private static String FILE_EXTENSION_PDF= ".pdf";
	private static String FILE_EXTENSION_ZIP = ".zip";

	private static String APP_NAME = "ZipAndLoad";

	private static int BUFFER = 262144;
	private static int zipMaxThread = 0;
	private static int loadMaxThread = 0;
	private static int csvLoaded = 0;

	private static boolean debugOn = cfgObj.zipAndLoad_Debug_On;

	public static final String PROCESS_ZIP = "ZIP";
	public static final String PROCESS_LOAD = "LOAD";

	private static String     dbUser = cfgObj.zipAndLoad_DB_User;
	private static String     dbPassword = cfgObj.zipAndLoad_DB_Password;
	private static Connection conn = null;


	/**
	 * main()
	 */
	public static void main(String[] argv) {

		//  Start of Transfer Functionality
		java.util.Date startTime = new java.util.Date();

		// Get Arguments Start
		// ****************************************************************************************************
		String strZipMaxThread="";
		String strLoadMaxThread="";
		String strMode="BOTH";

		for (int optind = 0; optind < argv.length; optind++) {

		  if (argv[optind].toLowerCase().indexOf("zipthreadcount") != -1) {
			strZipMaxThread = argv[optind];
			strZipMaxThread = strZipMaxThread.substring(strZipMaxThread.indexOf("=")+1);
			zipMaxThread = Integer.parseInt(strZipMaxThread);
		  } else
		  if (argv[optind].toLowerCase().indexOf("loadthreadcount") != -1) {
			strLoadMaxThread = argv[optind];
			strLoadMaxThread = strLoadMaxThread.substring(strLoadMaxThread.indexOf("=")+1);
			loadMaxThread = Integer.parseInt(strLoadMaxThread);
		  } else
		  if (argv[optind].toLowerCase().indexOf("mode") != -1) {
			strMode = argv[optind].toUpperCase();
			strMode = strMode.substring(strMode.indexOf("=")+1);
		  } else
		  if (argv[optind].toLowerCase().startsWith("-h")) {
			printUsage();
			System.exit(1);
		  } else {
			break;
		  }
		}

		if (zipMaxThread <= 0){
			zipMaxThread = 1;
		}
		if (loadMaxThread <= 0){
			loadMaxThread = 1;
		}

		// ****************************************************************************************************
		// Get Arguments End

		// Display Configuration
		cfgObj.write("\n----------------------------------------------------------" +
				   "\nConfiguration" +
				   "\n----------------------------------------------------------" +
				   "\n  Process Mode = " + strMode +
				   "\n" +
				   "\n  ZIP Process" +
				   "\n  ------------" +
				   "\n    Input Directory  : " + zipInDirectory +
				   "\n    Output Directory : " + zipOutDirectory +
				   "\n    Done Directory   : " + zipDoneDirectory +
				   "\n    Error Directory  : " + zipErrorDirectory +
				   "\n    Max Thread       : " + zipMaxThread +
				   "\n  " +
				   "\n  LOAD Process" +
				   "\n  ------------" +
				   "\n    Input Directory  : " + loadInDirectory +
				   "\n    Output Directory : " + loadOutDirectory +
				   "\n    Done Directory   : " + loadDoneDirectory +
				   "\n    Error Directory  : " + loadErrorDirectory +
				   "\n    Load Max Thread  : " + loadMaxThread +
				   "\n----------------------------------------------------------" +
				   "\n", APP_NAME);

		// Call Zip and Load Functions
		cfgObj.write("**********************************************************\n", APP_NAME);
		if (strMode.equals("BOTH")||strMode.equals("ZIP")){
			processFiles(PROCESS_ZIP);
			cfgObj.write("**********************************************************\n", APP_NAME);
		}
		if (strMode.equals("BOTH")||strMode.equals("LOAD")){
			processFiles(PROCESS_LOAD);
		}
		cfgObj.write("**********************************************************\n", APP_NAME);

		//  Start of Transfer Functionality
		java.util.Date endTime = new java.util.Date();
		// Display start and end times of Execution
		cfgObj.write("\n----------------------------------------------------------" +
				   "\n\nCSV loaded : " + csvLoaded +
				   "\n", APP_NAME);
		// Display start and end times of Execution
		cfgObj.write("\n----------------------------------------------------------" +
				   "\nStart of Execution : " + startTime +
				   "\nEnd   of Execution : " + endTime +
				   "\n\n", APP_NAME);

		System.exit(1);
	}

	/**
	 * processFiles()
	 */
	public static void processFiles(String processType){

		try {

			cfgObj.write("Process Name(processFiles) - Starting " + processType + "\n", APP_NAME);

			final String pType = processType;

			String strDirectory = zipInDirectory;
			int maxThread = zipMaxThread;

			if (pType.equals(PROCESS_LOAD)){
				strDirectory = loadInDirectory;
				maxThread = loadMaxThread;
			}

			File dir = new File(strDirectory);

			// Filter Start
			// ****************************************************************************************************

			FilenameFilter filter = new FilenameFilter() {
				public boolean accept(File dir, String name) {
					String tmpName = name.toLowerCase();
					String tmpFileExtension = FILE_EXTENSION_XLS;
					if (pType.equals(PROCESS_LOAD)){
						tmpFileExtension = FILE_EXTENSION_ZIP;
					}
					return tmpName.endsWith(tmpFileExtension);
				}
			};
			String[] files = dir.list(filter);

			// ****************************************************************************************************
			// Filter End

			Vector vecFiles = splitFiles(files,maxThread);

			Vector vecThreads = new Vector();

			int vecFilesSize = vecFiles.size();
			for (int i=0; i<vecFilesSize; i++) {
				SplitFilesDataBean dataBean = (SplitFilesDataBean)vecFiles.elementAt(i);

				if (pType.equals(PROCESS_LOAD)){
					LoadThread thread = new LoadThread(files, dataBean);
					thread.start();
					vecThreads.add(thread);

				} else{
					ZipThread thread = new ZipThread(files, dataBean);
					thread.start();
					vecThreads.add(thread);
				}

			}

			boolean	isDone = false;

			while (!isDone){
				Thread.sleep(1000);
				int vecThreadsSize = vecThreads.size();
				int threadDoneCount = 0;

				for (int i=0; i<vecFilesSize; i++) {
					Thread tmpThread = (Thread)vecThreads.elementAt(i);
					if (!tmpThread.isAlive()){
						threadDoneCount++;
					}
				}

                if (threadDoneCount == vecThreadsSize){
					isDone = true;
				}
			}

		} catch (Exception ex) {
			ex.printStackTrace();
			cfgObj.write("Process Name(processFiles) - " + ex.getClass() + " " + ex.getMessage() + "\n", APP_NAME);
		} finally{
			cfgObj.write("Process Name(processFiles) - Exiting " + processType + "\n\n", APP_NAME);
		}
	}

	/**
	 * addToZip()
	 */
    private static boolean addToZIP(File inputFile, String fileName, ZipOutputStream zipOut) {
		try{

			FileInputStream fi = new FileInputStream(inputFile);
			BufferedInputStream origin = new BufferedInputStream(fi, BUFFER);

			ZipEntry entry     = new ZipEntry(fileName);

			zipOut.putNextEntry(entry);

			byte data[] = new byte[BUFFER];

			int count;
			while((count = origin.read(data, 0, BUFFER)) != -1) {
				zipOut.write(data, 0, count);
			}
			origin.close();
			fi.close();

			return true;
		} catch (Exception ex) {
			ex.printStackTrace();
			cfgObj.write("Process Name(addToZIP) - " + ex.getClass() + " " + ex.getMessage() + "\n", APP_NAME);
			return false;
		}
    }

	/**
	 * splitFiles()
	 */
	private static Vector splitFiles(String[] files, int maxThread){
		Vector vecFiles = new Vector();
		int vsize = files.length;

		int filesPerThread = 0;

		if(vsize%maxThread==0){
			filesPerThread = vsize/maxThread;
		}else{
			filesPerThread = (vsize/maxThread)+1;
		}

		int count = 0;
		for(int i=0;i<vsize;i++){
			if((++count == filesPerThread) || ((i+1) == vsize)){
				SplitFilesDataBean dataBean = new SplitFilesDataBean();

				dataBean.setData((i+1)-count,i+1);
				vecFiles.addElement(dataBean);

				if (count == filesPerThread){
					count = 0;
				}
			}
		}
		return vecFiles;
	}

	/**
	 * moveFile()
	 */
	private static boolean moveFile(String fileName, String srcDirectory, String destDirectory){

		File file = new File(srcDirectory + fileName);

		if (file.exists()) {
			file.renameTo(new File(destDirectory + fileName));
		}

		return true;
	}

	/**
	 * moveFiles()
	 */
	private static boolean moveFiles(String[] files, String srcDirectory, String destDirectory){

		for (int j=0; j<files.length; j++) {
			String fileName = files[j];
			File file = new File(srcDirectory + fileName);

			if (file.exists()) {
				file.renameTo(new File(destDirectory + fileName));
			}
		}

		return true;
	}

	/**
	 * Class ZipThread
	 */
	private static class ZipThread extends Thread {
		String[] files;
		SplitFilesDataBean dataBean = new SplitFilesDataBean();

		/**
		 * ZipThread()
		 */
		ZipThread(String[] files, SplitFilesDataBean splitDataBean) {
			this.files = files;
			this.dataBean = splitDataBean;
		}

		/**
		 * run()
		 */
		public void run() {

			int intStartIdx = dataBean.getIntStartIdx();
			int intEndIdx = dataBean.getIntEndIdx();

			boolean errorFound = false;

			try{

			for(int i=intStartIdx;i<intEndIdx;i++){

				// Zip Start
				// ****************************************************************************************************

				String xlsFileName = "";
				String[] csvFiles = null;
				String[] pdfFiles = null;

				errorFound =false;

				try{
					final String fileName = files[i].substring(0,files[i].indexOf(FILE_EXTENSION_XLS));

					String zipFileName = fileName + FILE_EXTENSION_ZIP;
					FileOutputStream dest = new FileOutputStream(zipOutDirectory + zipFileName);
					ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(dest));

					// Add XLS
					xlsFileName = fileName + FILE_EXTENSION_XLS;
					File xlsFile = new File(zipInDirectory + xlsFileName);

					if (xlsFile.exists()) {
						addToZIP(xlsFile, xlsFileName, zipOut);
					} else{
						cfgObj.write("Process Name(ZipThread) - XLS file '" + xlsFileName + "' does not exist\n", APP_NAME);
						errorFound = true;
					}

					// Add CSV
					File csvDir = new File(zipInDirectory);

					// Filter Start
					FilenameFilter csvFilter = new FilenameFilter() {
						public boolean accept(File dir, String name) {
							String tmpName = name.toLowerCase();
							return (tmpName.startsWith(fileName.toLowerCase()) && tmpName.endsWith(FILE_EXTENSION_CSV));
						}
					};

					csvFiles = csvDir.list(csvFilter);

					String strTemp = "";

					for (int j=0; j<csvFiles.length; j++) {

						String csvFileName = csvFiles[j];
						File csvFile = new File(zipInDirectory + csvFileName);

						strTemp += "\n" + csvFileName;

						if (csvFile.exists()) {
							addToZIP(csvFile, csvFileName, zipOut);
						} else{
							cfgObj.write("Process Name(ZipThread) - CSV file '" + csvFileName + "' does not exist\n", APP_NAME);
							errorFound = true;
						}
					}

					// Add PDF
					File pdfDir = new File(zipInDirectory);

					// Filter Start
					FilenameFilter pdfFilter = new FilenameFilter() {
						public boolean accept(File dir, String name) {
							String tmpName = name.toLowerCase();
							return (tmpName.startsWith(fileName.toLowerCase()) && tmpName.endsWith(FILE_EXTENSION_PDF));
						}
					};

					pdfFiles = pdfDir.list(pdfFilter);

					//String strTemp = "";

					for (int j=0; j<pdfFiles.length; j++) {

						String pdfFileName = pdfFiles[j];
						File pdfFile = new File(zipInDirectory + pdfFileName);

						strTemp += "\n" + pdfFileName;

						if (pdfFile.exists()) {
							addToZIP(pdfFile, pdfFileName, zipOut);
						} else{
							cfgObj.write("Process Name(ZipThread) - PDF file '" + pdfFileName + "' does not exist\n", APP_NAME);
							errorFound = true;
						}
					}

/*
					if (csvFiles.length <= 0){
						errorFound = true;
					}
*/

					if (zipOut != null){
						zipOut.close();
					}
					zipOut = null;

					if (dest != null){
						dest.close();
					}
					dest = null;

					if (debugOn){
						cfgObj.write("\nZip file " + xlsFileName + " created with the following contents:" +
							"\n" + xlsFileName + strTemp +
							"\n", APP_NAME);
					}

				} catch(Exception e){
					errorFound = true;
				}

				if (errorFound){
					moveFile(xlsFileName, zipInDirectory , zipErrorDirectory);
					moveFiles(csvFiles, zipInDirectory , zipErrorDirectory);
					moveFiles(pdfFiles, zipInDirectory , zipErrorDirectory);
				} else{
					moveFile(xlsFileName, zipInDirectory , zipDoneDirectory);
					moveFiles(csvFiles, zipInDirectory , zipDoneDirectory);
					moveFiles(pdfFiles, zipInDirectory , zipDoneDirectory);
				}
				// ****************************************************************************************************
				//Zip End

			}// end for

        }catch (Exception ex) {
			ex.printStackTrace();
			cfgObj.write("Process Name(ZipThread) - " + ex.getClass() + " " + ex.getMessage() + "\n", APP_NAME);
        }finally {
			cfgObj.write("Process Name(ZipThread) - Exiting " + this.toString() + "\n", APP_NAME);
        }// end try
      }//end run
    }// end class

	/**
	 * Class LoadThread
	 */
	private static class LoadThread extends Thread {
		String[] files;
		SplitFilesDataBean dataBean = new SplitFilesDataBean();

		/**
		 * LoadThread()
		 */
		LoadThread(String[] files, SplitFilesDataBean splitDataBean) {
			this.files = files;
			this.dataBean = splitDataBean;
		}

		/**
		 * run()
		 */
		public void run() {

			int intStartIdx = dataBean.getIntStartIdx();
			int intEndIdx = dataBean.getIntEndIdx();

			boolean errorFound = false;

			try{

			openOracleConnection();

			for(int i=intStartIdx;i<intEndIdx;i++){

				// Load Start
				// ****************************************************************************************************

				String loadFileName = "";

				errorFound =false;

				try{
					final String fileName = files[i].substring(0,files[i].indexOf(FILE_EXTENSION_ZIP));

					loadFileName = fileName + FILE_EXTENSION_ZIP;

					if (debugOn){
						cfgObj.write("\nZip file " + loadFileName + " to be loaded\n", APP_NAME);
					}

					java.util.StringTokenizer strTokenizer = new java.util.StringTokenizer(fileName, "_");

					if(strTokenizer.countTokens() == 3) {

						String stmtDate = strTokenizer.nextToken();
						String acctID = strTokenizer.nextToken();
						String instanceID = strTokenizer.nextToken();

						try{
							writeBLOBPut(Integer.parseInt(instanceID),acctID,stmtDate,loadInDirectory + loadFileName);
						} catch (Exception e){
							errorFound = true;
						}
					}

				} catch (Exception e){
					errorFound = true;
				}

				if (errorFound){
					moveFile(loadFileName, loadInDirectory , loadErrorDirectory);
				} else{
					moveFile(loadFileName, loadInDirectory , loadDoneDirectory);
				}

				// ****************************************************************************************************
				//Load End

			}// end for
			csvLoaded += intEndIdx;
			closeOracleConnection();

        }catch (Exception ex) {
			ex.printStackTrace();
			cfgObj.write("Process Name(LoadThread) - " + ex.getClass() + " " + ex.getMessage() + "\n", APP_NAME);
        }finally {
			cfgObj.write("Process Name(LoadThread) - Exiting " + this.toString() + "\n", APP_NAME);
        }// end try
      }//end run
    }// end class

	/**
	 * Obtain a connection to the Oracle database.
	 * @throws java.sql.SQLException
	 */
	public static void openOracleConnection()
			throws    SQLException
					, IllegalAccessException
					, InstantiationException
					, ClassNotFoundException {

		String driver_class  = "oracle.jdbc.driver.OracleDriver";
		String connectionURL = null;

		try {
			Class.forName (driver_class).newInstance();
			connectionURL = cfgObj.zipAndLoad_DB_URL;
			conn = DriverManager.getConnection(connectionURL, dbUser, dbPassword);
			conn.setAutoCommit(false);
			cfgObj.write("Process Name(openOracleConnection) - DB Connection Opened\n", APP_NAME);
		} catch (IllegalAccessException e) {
			cfgObj.write("Process Name(openOracleConnection) - " + e.getClass() + " " + e.getMessage() + "\n", APP_NAME);
			throw e;
		} catch (InstantiationException e) {
			cfgObj.write("Process Name(openOracleConnection) - " + e.getClass() + " " + e.getMessage() + "\n", APP_NAME);
			throw e;
		} catch (ClassNotFoundException e) {
			cfgObj.write("Process Name(openOracleConnection) - " + e.getClass() + " " + e.getMessage() + "\n", APP_NAME);
			throw e;
		} catch (SQLException e) {
			cfgObj.write("Process Name(openOracleConnection) - " + e.getClass() + " " + e.getMessage() + "\n", APP_NAME);
			throw e;
		}

	}


	/**
	 * Close Oracle database connection.
	 * @throws java.sql.SQLException
	 */
	public static void closeOracleConnection() throws SQLException {

		try {
			conn.close();
			cfgObj.write("Process Name(closeOracleConnection) - DB Connection Closed\n", APP_NAME);
		} catch (SQLException e) {
			cfgObj.write("Process Name(closeOracleConnection) - " + e.getClass() + " " + e.getMessage() + "\n", APP_NAME);
			if (conn != null) {
				try {
					conn.rollback();
				} catch (SQLException e2) {
					cfgObj.write("Process Name(closeOracleConnection) - " + e2.getClass() + " " + e2.getMessage() + "\n", APP_NAME);
				}
			}
			throw e;
		}

	}

    /**
     * Method used to write binary data contained in a file to an Oracle BLOB
     * column. The method used to write the data to the BLOB uses the putBytes()
     * method. This is one of two types of methods used to write binary data to
     * a BLOB column. The other method uses Streams.
     *
     * @throws java.io.IOException
     * @throws java.sql.SQLException
     */
    public static void writeBLOBPut(int instanceID, String acctID, String stmtDate, String fileName)
            throws IOException, SQLException {

        FileInputStream     inputFileInputStream    = null;
        String              sqlText                 = null;
        Statement           stmt                    = null;
        ResultSet           rset                    = null;
        BLOB                image                   = null;
        int                 chunkSize;
        byte[]              binaryBuffer;
        long                position;
        int                 bytesRead               = 0;
        int                 bytesWritten            = 0;
        int                 totbytesRead            = 0;
        int                 totbytesWritten         = 0;

        try {

            stmt = conn.createStatement();

            File inputBinaryFile = new File(fileName);
            inputFileInputStream = new FileInputStream(inputBinaryFile);

            sqlText =
                "INSERT INTO statementext (instanceID, acctID, stmtDate, zipBLOB, loadDT) " +
                "   VALUES(" + instanceID + ", '" + acctID + "', '" + stmtDate + "', EMPTY_BLOB(), SYSDATE)";
            stmt.executeUpdate(sqlText);

            sqlText =
                "SELECT zipBLOB " +
                "FROM   statementext " +
                "WHERE  instanceID = '" + instanceID + "'" +
                "FOR UPDATE";
            rset = stmt.executeQuery(sqlText);
            rset.next();
            image = ((OracleResultSet) rset).getBLOB("zipBLOB");

            chunkSize = image.getChunkSize();
            binaryBuffer = new byte[chunkSize];

            position = 1;
            while ((bytesRead = inputFileInputStream.read(binaryBuffer)) != -1) {
                //bytesWritten = image.putBytes(position, binaryBuffer, bytesRead);
                bytesWritten = image.putBytes(position, binaryBuffer);
                position        += bytesRead;
                totbytesRead    += bytesRead;
                totbytesWritten += bytesWritten;
            }

            conn.commit();
            rset.close();
            stmt.close();

            if (debugOn){
				cfgObj.write("\nWrote file " + inputBinaryFile.getName() + " to BLOB column." +
					"\n" + totbytesRead + " bytes read." +
					"\n" + totbytesWritten + " bytes written.\n", APP_NAME);
			}

        } catch (IOException e) {
			cfgObj.write("Process Name(writeBLOBPut) - " + e.getClass() + " " + e.getMessage() + "\n", APP_NAME);
            throw e;
        } catch (SQLException e) {
			cfgObj.write("Process Name(writeBLOBPut) - " + e.getClass() + " " + e.getMessage() + "\n", APP_NAME);
            throw e;
        } finally {
			if (inputFileInputStream != null){
	            inputFileInputStream.close();
			}
		}

    }

	public static void printUsage() {
		System.out.println("\nCopyright (c) 2007 MYeBills Consulting Sdn Bhd (739522-W).");
		System.out.println("All rights reserved.");
		System.out.println("\n\nUsage : java -server -Xms[HS] -Xmx[HS] -DBGROOT=[BGROOT] com.fsbm.ctech.maxis.ebpsbackend.ZipAndLoad -zipthreadcount=[X] -loadthreadcount=[X] -mode=[MODE]");
		System.out.println("\n\nWhere:");
		System.out.println("\t -[HS] Min and Max Heap Memory size allocation");
		System.out.println("\t -[BGROOT] CheckFree installation directory");
		System.out.println("\t -[X] Max thread count");
		System.out.println("\t -[MODE] Either 'ZIP' only, 'LOAD' only, or 'BOTH'");
		System.out.println("\n\tor java com.fsbm.ctech.maxis.ebpsbackend.ZipAndLoad -h for help options");
	}


}
